package se19team1e.search.props;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import se19team1e.dao.abstrakt.jaas.JobAdvertDAO;
import se19team1e.dao.factory.DAOFactorySelector;
import se19team1e.entity.JobAdvertDTO;
import se19team1e.exception.DataAccessException;
import se19team1e.search.SearchEngine;
import se19team1e.util.Constants;
import se19team1e.util.ReflectionUtils;

/**
 * Search Engine to look up for the job Advert specified by criteria. It uses
 * the Rabin Karp Search Algorithm in implementation. It is the concrete
 * implementation of the search engine class and can be swap out in future for
 * better search algorithm.
 * 
 * @param JobAdvertDTO
 *            The Job advert DTO class and the string for the search criteria
 * 
 * 
 * @author Arun Manivannan
 * 
 */
public class JobAdvertPropertySearchEngine implements
		SearchEngine<JobAdvertDTO, String> {
	/**
	 * Filter will handle the cases of non-advertiser and advertiser.
	 * Non-advertiser will not be able to see withdrawn job advertisements
	 */
	private Map<String, String> index = new HashMap<String, String>();
	/**
	 * This map will store all the DTO in a map for the search engine to use
	 */
	private Map<String, JobAdvertDTO> allDTOs = new HashMap<String, JobAdvertDTO>();
	/**
	 * Create a new static instance of job advert property search engine
	 * 
	 * */
	private static JobAdvertPropertySearchEngine instance = new JobAdvertPropertySearchEngine();

	/**
	 * To get an instance of job advert property search engine
	 * 
	 * @return instance of the job advert property search engine
	 */
	public static JobAdvertPropertySearchEngine getInstance() {
		instance.refreshIndex();
		return instance;
	}

	/**
	 * Default constructor for job advert property search engine
	 */
	private JobAdvertPropertySearchEngine() {
	}

	/**
	 * Add to the search engine, the new job advert DTO. Future versions can
	 * have actual addition to the search engine.
	 * 
	 * @param searchObject
	 *            advert DTO that is to be added to the search engine
	 */
	@Override
	public void addToIndex(JobAdvertDTO searchObject) {
		refreshIndex();
	}

	/**
	 * Update the index with new Job Advert DTO. Future version can have actual
	 * update to the search engine.
	 * 
	 * @param searchObject
	 *            advert DTO that is to be updated by search engine
	 */
	@Override
	public void updateIndex(JobAdvertDTO searchObject) {
		refreshIndex();

	}

	/**
	 * Remove a Job advert DTO from the data storage. In this project work, it
	 * will refresh index. For future implementation, actual deletion can be
	 * done.
	 * 
	 * @param searchObject
	 *            advert DTO that is to be remove from search engine
	 */
	@Override
	public void removeFromIndex(JobAdvertDTO searchObject) {
		refreshIndex();
	}

	/**
	 * Search engine that will use a filter and search criteria to find the job
	 * advert in the data storage
	 * 
	 * @param criteria
	 *          The search criteria set by user
	 * @param filters
	 * 			The filters that are to be applied, rules such as non-advertiser cannot see withdrawn job adverts
	 * @return List of the Job Advert DTO that will display in the table which
	 *         match the filter and criteria
	 */
	@Override
	public List<JobAdvertDTO> search(String criteria, Map<String,String> filters) {
		
		List<JobAdvertDTO> jobAdverts=new ArrayList<JobAdvertDTO>();
		Set<JobAdvertDTO> matchingJobAdverts=new HashSet<JobAdvertDTO>();
		
		filters = resolveFilter(filters);
		
		for (Entry<String,String> indexEntry:index.entrySet()) {
			
			boolean found = RabinKarpStringSearchUtil.find(criteria, indexEntry.getKey());
			if (found){
				
				String id = indexEntry.getValue();
				System.out.println("Adding ID @@@@@@@@@@@: "+id);

				JobAdvertDTO jobAdvertDTO = allDTOs.get(id);
				boolean filterMatch=applyFilter(filters,jobAdvertDTO);
				if (!filterMatch) continue;
				
				matchingJobAdverts.add(jobAdvertDTO);
			}
		}
			
		if (matchingJobAdverts!=null){
			jobAdverts.addAll(matchingJobAdverts);
		}
		return jobAdverts;
			
	}

	/**
	 * Resolve filter using map of string, string. If it is null, a new instance
	 * of filters will be created
	 * 
	 * @param filters
	 *            the search criteria which may be null
	 * @return filters in a form of Map (string,string)
	 */
	private Map<String, String> resolveFilter(Map<String, String> filters) {
		if (filters == null) {
			filters = new HashMap<String, String>();
		}
		addDefaultFilter(filters);
		return filters;
	}

	/**
	 * Default search filter if user presses search will be given as deleted and
	 * false. This handles the case when withdraw or deleted job advert are not
	 * to be shown.
	 * 
	 * @param filters
	 *            If entry is deleted and search is not found
	 */
	private void addDefaultFilter(Map<String, String> filters) {

		filters.put("deleted", "false");
	}

	/**
	 * Apply filter to the Job Advert DTO before doing search using the Rabin
	 * Karp Search Algorithm
	 * 
	 * @param filters
	 *            Filters which is in a map of string,string.
	 * @param jobAdvertDTO
	 *            The job advert DTO that will converted to string for search
	 * 
	 * @return true if search is able to return some information
	 */
	private boolean applyFilter(Map<String, String> filters,
			JobAdvertDTO jobAdvertDTO) {

		String rawToString = ReflectionUtils.toString(jobAdvertDTO);

		boolean good = true;

		String constructCriteria = null;
		for (Entry<String, String> filterEntry : filters.entrySet()) {
			constructCriteria = filterEntry.getKey() + Constants.EQUALS
					+ filterEntry.getValue();

			boolean tmpGood = RabinKarpStringSearchUtil.find(constructCriteria,
					rawToString);

			good = good && tmpGood;

			if (tmpGood == false) {
				break;
			}
		}

		return good;
	}

	/**
	 * Build and refresh Index for the search engine for the job advertisement
	 * 
	 * @exception DataAccessException
	 *                If any problems when constructing the search engine
	 */
	@Override
	public void refreshIndex() {

		System.out.println("Building Index.....");
		JobAdvertDAO jobAdvertDAO = DAOFactorySelector.getDAOFactory()
				.getJobAdvertDAO();
		List<JobAdvertDTO> allJobAdvertDAOs;
		try {
			allJobAdvertDAOs = jobAdvertDAO.retrieveAll();
			for (JobAdvertDTO jobAdvertDTO : allJobAdvertDAOs) {

				allDTOs.put(jobAdvertDTO.getId(), jobAdvertDTO);
				index.put(ReflectionUtils.valuesToString(jobAdvertDTO),
						jobAdvertDTO.getId());
			}
		} catch (DataAccessException e) {
			System.err
					.println("Data Access Exception while constructing the Search engine");
			e.printStackTrace();
		}

	}

}
